// 闭包：一个函数有权访问另一个函数作用域中的变量。
// 作用：可以用来隐藏变量，避免全局污染。也可以用于读取函数内部的变量。
// 缺点：导致变量不会被垃圾回收机制回收，造成内存消耗。

// 闭包函数
function fn() {
    let a = 10;

    function b() {
        //此时的a是函数fn的a
        a = a * 10;
        return a;
    }
    //将函数作为返回值返回
    return b;
}
// 调用方式
var baz = fn();
//由于连续调用闭包函数，变量a并没有被销毁所以会连续做*10操作
console.log(baz()); //输出100
console.log(baz()); //输出1000
console.log(baz()); //输出10000
/** 解释：1. 首先有执行上下文fn,在foo中定义了函数b,通过对外返回的方式让b执行。
 *        2.当b执行时，访问了fn内部的变量a。因此闭包产生
 * 
 * *扩展*：js的垃圾回收机制：当一个值，在内存中失去引用时，垃圾回收机制会根据特殊的算法找到它，并将其回收，释放内存。
 * 
 * *函数的执行上下文*：在执行完毕后，生命周期结束，那么该函数的执行上下文就会失去引用。其占用的内存空间很快就会被垃圾回收器释放。可是闭包的存在会阻止这一过程。
 */

// 将以下程序使用闭包进行输出
/**
 * ∵ 回调函数是在循环结束之后才会被执行（js的执行机制）
 * 因此：以下程序会依次输出5个6。
 */
for (let i = 1; i <= 5; i++) {
    setTimeout(function timer() {
        console.log("修改前：" + i);
    }, i * 1000);
}
// 改为以下：—
/**
 * 1. 使用自执行函数提供闭包条件
 * 2. 传入i值并保存在闭包中
 * 输出结果：会依次输出1-5
 */
for (let i = 1; i <= 5; i++) {
    (function(j) { // 此处 j为形参
        setTimeout(function timer() {
            console.log("修改后：" + j);
        }, j * 1000);
    })(i) // 此处 i为实参
}
// 或者：在setTimeout的第一个参数处利用闭包
for (let i = 0; i <= 5; i++) {
    setTimeout((function(i) {
        return function() {
            console.log(i);
        }
    })(i), i * 1000);
}
// 或者：使用let (本质上是将一个块转换成一个可以被关闭的作用域,即块级作用域)
for (var i = 1; i <= 5; i++) {
    let j = i; // 是的，闭包的块作用域! 
    setTimeout(function timer() {
        console.log(j);
    }, j * 1000);
}
// 或者：for() 中使用let 而不是var （原理：使用let: 变量在循环过程中，每次迭代都会声明。随后每个迭代都会使用上一个迭代结束时的值来初始化这个变量。）
for (let i = 1; i <= 5; i++) {
    setTimeout(function timer() {
        console.log(i);
    }, i * 1000);
}
/**
 * 对for循环中不用var而使用let的解释：
 */

/**
 * 1. 当for循环中使用let关键字的时候，在这个()块中会生成一个作用域，我们称之为词法作用域或者块级作用域。ES5之前只有global scope和 function scope，var 声明的变量会登记在最近的上述作用域中。
 * 例如：for(let i = 0;i < 5;i++) 这句话的圆括号之间，有一个隐藏的作用域。
 */
for (let i = 0; i < 10; i++) // 作用域A
{ // 作用域B
    console.log(i);
}
/*
    作用域如下:
     A: {
        B: { 
            i 变量在B作用域中
            // 10个副本
        }
     }
 */

/**
 * 2. 在for循环中变量i是由var声明的，所以变量i会被提升至for循环外的作用域顶部，所以即使是在循环外也可以访问变量i。 例如：
 */
for (var i = 0; i < 10; i++) {
    process(items[i])
}
console.log(i); // 这里仍然可以访问变量i, 但如果换成let这里不可以访问变量i,抛出错误。
/**
 * 3. for(let i = 0;i < 10;i++){循环体} 在每次执行循环体之前，js引擎会把i在循环体的上下文中重新声明及初始化一次。
 * 结合代码块一来看，10个副本B作用域中都会重新声明及初始化一次i,那么如果B作用域中如果是一个函数setTimeout(()=>{console.log(i)})，产生了闭包。
 * 那么闭包中的i仍然保持着对外部B作用域中活动对象i的引用。
 * 上述例子使用let 输出的是0~5
 */

/**
 * 4. 解释为什么for(var i = 0;i <= 5;i++){setTimeout(()=>{console.log(i)})} 输出是6个6。
 * 在每次执行循环体之前，i并不会在循环体上下文中重新声明和初始化一次，这个步骤在i实际所在外部就近函数或者全局函数中完成。
 * 因此，当A作用域中如果是一个函数setTimeout(()=>{console.log(i)})，产生了闭包。按照闭包中i仍然保持着对外部A作用域中活动对象i的引用逻辑，但是作用域A中并没用i。
 * 所以，顺着作用域链往上找，直到找到实际i所在外部就近函数或者全局函数，找到了还需要等循环走完（这里看js的执行机制：所有的回调函数是在循环结束后才会被执行。），返回实际i为6的值。
 */

/**
 * 5. 我们用一个代码块来补充4中的解释。我们可以看到如果我们把i赋值给j并放入A作用域中，如果内部还是闭包，那么根据闭包原则，依旧能完成对A作用域中变量j的引用，所以能得到我们想要的结果。 
 */
for (var i = 0; i < 10; i++)
// 作用域A
{
    let j = i
    console.log(j); // 依次输出 0 ~ 5
}